import fs from "fs";
import FormData from "form-data";
import path from "path";
import Conf from "conf";
import semver from "semver";
import format from "string-template";
import axios from "axios";
import { execSync } from "child_process";
import { createRequire } from "node:module";
import conventionalChangelog from "conventional-changelog";
import logger from "./logger.js";

const require = createRequire(import.meta.url);
const { name } = require("../package.json");

export const config = new Conf({
  projectName: name,
  schema: {
    apiPrefix: {
      type: "string",
      default: "https://gitee.com/api/v5/",
    },
    locale: {
      type: "string",
      default: process.env.LANG ? process.env.LANG.split(".")[0] : "en_US",
    },
    accessToken: {
      type: "string",
    },
  },
});

const locales = {
  en_US: {
    fix: "Bug Fixes",
    feat: "Features",
    perf: "Performance Improvements",
    breaking_changes: "BREAKING CHANGES",
    first_release: "First release.",
    no_description: "No description.",
    title: "{name} {version} released, {summary}",
    summary: {
      regular: "regular updates",
      many_bug_fixes: "many bug fixes",
      many_features: "many features",
      breaking_changes: "breaking changes",
      performance_improvements: "performance improvements",
    },
  },
  zh_CN: {
    fix: "Bug 修复",
    feat: "新特性",
    perf: "性能优化",
    breaking_changes: "重要改动",
    first_release: "第一个发行版。",
    no_description: "无描述。",
    title: "{name} {version} 发布, {summary}",
    summary: {
      regular: "常规更新",
      many_bug_fixes: "大量 bug 修复",
      many_features: "大量新特性",
      breaking_changes: "重要改动",
      performance_improvements: "性能优化",
    },
  },
};

function getTranslation() {
  let locale = config.get("locale");

  if (!(locale in locales)) {
    locale = "en_US";
  }
  return locales[locale];
}

if (!fs.existsSync(path.join(process.cwd(), "package.json"))) {
  logger.error("package.json does not exist");
  process.exit(1);
}
const PKG = JSON.parse(
  fs.readFileSync(path.join(process.cwd(), "package.json"))
);
const INITIAL_VERSION = "1.0.0";
const TAG_PREFIX = "v";

function getReleaseUrl(info) {
  return `${config.get("apiPrefix")}repos/${info.owner}/${info.repo}/releases`;
}

function getReleaseParams(params) {
  if (!config.get("accessToken")) {
    throw new Error("access token does not exist");
  }
  return {
    access_token: config.get("accessToken"),
    ...params,
  };
}

async function fetchLatestRelease(info) {
  const url = `${getReleaseUrl(info)}/latest`;
  logger.log(`Fetching latest release from ${url}`);
  const resp = await axios.get(url, { params: getReleaseParams() });
  return resp.data;
}

async function fetchReleaseByTag(info, tagName) {
  const url = getReleaseUrl(info);
  const resp = await axios.get(url, { params: getReleaseParams() });
  return resp.data.find((release) => release.tag_name === tagName);
}

async function fetchLatestVersion(info) {
  try {
    const release = await fetchLatestRelease(info);
    if (release.tag_name && release.tag_name.charAt(0) === "v") {
      const version = release.tag_name.slice(1);
      logger.log(`Latest version: ${version}`);
      return [version, release.target_commitish];
    }
  } catch (err) {
    logger.warning("Unable to get latest release");
  }
  return [];
}

async function generateReleaseNote(latestVersion, commitId) {
  let note = "";
  const gitOptions = {
    from: commitId,
  };

  if (!latestVersion) {
    return "";
  }
  logger.log(`Analyzing commit history from ${latestVersion}`);
  await new Promise((resolve, reject) => {
    conventionalChangelog(
      { preset: "angular" },
      { version: "{version}" },
      gitOptions,
      null,
      {
        linkReferences: false,
        commitsSort: "header",
      }
    )
      .on("data", (data) => {
        note += data.toString();
      })
      .on("error", reject)
      .on("end", resolve);
  });
  return note.trim();
}

export function getNextVersion({ changes, latestVersion, prerelease }) {
  let version = latestVersion;
  const prereleaseId = typeof prerelease === "string" ? prerelease : "beta";

  if (!latestVersion) {
    if (prerelease) {
      if (typeof prerelease === "string") {
        return semver(`${INITIAL_VERSION}-${prerelease}`);
      }
      return semver(`${INITIAL_VERSION}-beta`);
    }
    return semver(INITIAL_VERSION);
  }
  if (semver(version).prerelease.length > 0 && prerelease) {
    version = semver.inc(version, "prerelease", prereleaseId);
  } else {
    if (changes.breakingChanges) {
      version = semver.inc(version, "major");
    } else if (changes.features) {
      version = semver.inc(version, "minor");
    } else {
      version = semver.inc(version, "patch");
    }
    if (prerelease) {
      version += `-${prereleaseId}`;
    }
  }
  return version;
}

function getGitRepoInfo() {
  const info = { repo: "unknown", owner: "unknown" };
  const repo = PKG.repository;

  function parseUrl(url) {
    [info.owner, info.repo] = url.split(":")[1].split("/");
    if (info.repo.endsWith(".git")) {
      info.repo = info.repo.slice(0, -4);
    }
  }

  if (repo && repo.url && repo.type === "git") {
    parseUrl(repo.url);
  } else {
    try {
      parseUrl(
        execSync("git remote get-url origin", { encoding: "utf-8" }).trim()
      );
    } catch (_err) {
      return info;
    }
  }
  return info;
}

export async function uploadAsset(file, version) {
  const info = getGitRepoInfo();
  try {
    const url = getReleaseUrl(info);
    const release = await (version
      ? fetchReleaseByTag(info, `${TAG_PREFIX}${version}`)
      : fetchLatestRelease(info));

    if (!release?.id) {
      throw new Error("Release not found");
    }
    if (!fs.existsSync(file)) {
      throw new Error(`${file}: file does not exist`);
    }
    if (fs.statSync(file).isDirectory()) {
      throw new Error(`${file}: Unsupported directory upload`);
    }
    const formData = new FormData();
    const uploadUrl = `${url}/${release.id}/attach_files`;

    formData.append("file", fs.createReadStream(file));
    formData.append("access_token", config.get("accessToken"));
    logger.log(`Uploading file to ${uploadUrl}`);
    const resp = await axios.request({
      method: "post",
      maxBodyLength: Infinity,
      url: uploadUrl,
      headers: {
        "Content-Type": "multipart/form-data",
        Accept: "application/json",
        ...formData.getHeaders(),
      },
      data: formData,
    });
    logger.log(
      `Successfully uploaded asset file for release ${release.tag_name}`
    );
    logger.log(resp.data);
  } catch (err) {
    logger.error(err.message);
    if (err.response) {
      logger.error(err.response.data);
    }
  }
}

export async function collect(latestVersion) {
  let commitId;
  let version = latestVersion;
  const info = getGitRepoInfo();

  if (version) {
    commitId = `${TAG_PREFIX}${version}`;
  } else {
    [version, commitId] = await fetchLatestVersion(info);
  }

  const note = await generateReleaseNote(version, commitId);
  const messages = new Set();
  const changes = {
    bugs: 0,
    features: 0,
    breakingChanges: 0,
    performanceImprovements: 0,
  };
  let changesKey = null;

  const lines = note.split("\n").filter((line) => {
    if (!line) {
      return true;
    }
    if (changesKey && line.startsWith("*")) {
      if (changes[changesKey] >= 100) {
        return false;
      }

      let message = line.slice(1);
      const i = message.indexOf("(");

      if (i > 0) {
        message = message.substring(0, i).trim();
      }
      // 忽略重复的提交信息
      if (messages.has(message)) {
        return false;
      }
      changes[changesKey] += 1;
      messages.add(message);
    } else if (line.includes("BREAKING CHANGES")) {
      changesKey = "breakingChanges";
    } else if (line.includes("Features")) {
      changesKey = "features";
    } else if (line.includes("Bug Fixes")) {
      changesKey = "bugs";
    } else if (line.includes("Performance Improvements")) {
      changesKey = "performanceImprovements";
    }
    return true;
  });
  return {
    changes,
    note: lines.join("\n"),
    latestVersion: version,
    ...info,
  };
}

export function generate({
  prerelease = false,
  changes,
  note,
  version,
  owner,
  repo,
} = {}) {
  const info = {
    tag_name: INITIAL_VERSION,
    name: "",
    body: note,
    version,
    owner,
    repo,
    prerelease: !!prerelease,
    target_commitish: "master",
  };
  const t = getTranslation();

  if (info.body.trim().length < 2) {
    if (info.version === `v${INITIAL_VERSION}`) {
      info.body += `\n\n${t.first_release}`;
    } else {
      info.body += `\n\n${t.no_description}`;
    }
  }

  let summary = t.summary.regular;
  const changesCount = changes.bugs + changes.features;

  if (changes.breakingChanges > 0) {
    summary = t.summary.breaking_changes;
  } else if (
    changes.performanceImprovements > 4 &&
    changes.performanceImprovements > changesCount * 0.2
  ) {
    summary = t.summary.performance_improvements;
  } else if (changes.features > 10 && changes.features > changesCount * 0.4) {
    summary = t.summary.many_features;
  } else if (changes.bugs > 10 && changes.bugs && changesCount * 0.4) {
    summary = t.summary.many_bug_fixes;
  }
  info.tag_name = `${TAG_PREFIX}${info.version}`;
  info.name = format(t.title, {
    name: PKG.name,
    version: info.version,
    summary,
  });
  info.body = format(info.body, { version: info.version })
    .replace("{version}", info.version)
    .replace("BREAKING CHANGES", t.breaking_changes)
    .replace("Features", t.feat)
    .replace("Bug Fixes", t.fix)
    .replace("Performance Improvements", t.perf);
  return info;
}

export async function upload(info) {
  let response;
  const url = getReleaseUrl(info);
  const htmlUrl = `https://gitee.com/${info.owner}/${info.repo}/releases/${info.tag_name}`;
  const params = getReleaseParams(info);

  delete params.repo;
  delete params.owner;
  delete params.version;
  logger.log(`uploading release to ${url}`);
  return new Promise((resolve) => {
    axios.post(url, params).then(
      (res) => {
        logger.log(`the release has been uploaded, url: ${htmlUrl}`);
        resolve(res.data);
      },
      (err) => {
        response = err.response;
        if (response && response.data.message) {
          logger.error(response.data.message);
        } else {
          logger.error(err.message);
        }
      }
    );
  });
}
